251109 用Godot实现的行为树
简介
行为树是一种用来表达与实现复杂行为的工具,它具有以下特征:
- 行为树一定得是树:由节点和边构建,节点之间不存在闭环;假设下图的虚线真的连上了,它就不再是树了。
- 叶子节点代表可执行的行为:叶子节点是没有子节点的节点,是根节点之外的只有一条边的节点;它代表一个具体的行为,执行该行为后需要返回执行状态(成功、失败、运行中……)
- 非叶子节点控制树的遍历:内部节点根据子节点返回的状态,根据一定规则确定下一个执行的节点,返回自己的执行状态。

在游戏中,我们常用行为树制作NPC的AI,自机角色的控制也可通过行为树构建。
概念
流程
行为树执行一个节点可能返回三种基本状态:成功、失败和运行中。当前没有运行中的节点时,行为树的执行是从根节点开始的,根据一定规则遍历树中的节点,直到遇到运行中的节点;当前存在运行中的节点时,行为树就直接执行该节点,直到它执行结束。
此外,行为树可能出现运行中的节点被打断的操作。
想象一下这样的场景:一个太刀侠打出了登龙(气刃兜割),正在半空中迅速下坠,直到落地都无法做出其它任何行动;与此同时,另外一边,怪物使用了一个强而有力的技能击杀了太刀侠。
正常情况下,太刀侠应该会立刻停止登龙,进入战败CG;但如果这个行为树没考虑打断,太刀侠还会继续登龙直到落地,很壮烈但毕竟不合常理。
因此,打断往往也是设计行为树需要考虑的点。
节点
行为树有以下几种基本节点:
- 选择节点:顺序执行自己的子节点,遇到执行成功的节点时,停止继续往后执行子节点,自身返回成功;若子节点全部执行失败,则返回失败。
- 序列节点:顺序执行自己的子节点,遇到执行失败的节点时,停止继续往后执行子节点,自身返回失败;若子节点全部执行成功,则返回成功。
- 执行节点:可以分为仅返回成功、失败的条件节点与可能返回运行中的动作节点。一般来说,前者用于执行简单的判断,后者用于执行复杂的行动。不过笔者认为无需如此细分,至少不应被名称限制,认为任何实际行动都要通过动作节点执行,不能放在条件节点里,很快就能看到相关例子了。
此外,还有一些节点可能会为构建行为树带来便利:
- 装饰节点:有且仅有一个子节点,可以根据一定条件决定是否执行子节点,类似于序列节点与条件节点的组合,如下图所示;也可以对子节点的执行状态进行修饰,例如取反。
- 并行节点:允许多个子节点同时运行,根据子节点的运行结果决定自身的运行状态。

实现
最近笔者正在探索Godot,发现屏幕左侧的场景树居然是树状结构,树中的节点还能不包含空间信息;右侧的检查器窗口与树结构分离开,用于编辑数据十分清晰简洁,天然适合作为行为树的图形化编辑界面,故使用 Godot 4.4.1 Mono版本实现行为树。
节点的基类
using Godot;
namespace BehaviorTree;
[GlobalClass]
public abstract partial class BTNode : Node
{
public State CurrentState { get; private set; }
public enum State
{
Aborted,
Running,
Success,
Failure,
}
public void Tick(double delta)
{
if (CurrentState != State.Running) OnEnter();
CurrentState = Process(delta);
if (CurrentState != State.Running) OnExit();
}
public virtual bool Abort(AbortInfo info)
{
CurrentState = State.Aborted;
return true;
}
public virtual void Input(InputEvent input) { }
protected abstract State Process(double delta);
public virtual void Init() { }
protected virtual void OnEnter() { }
protected virtual void OnExit() { }
}
选择节点
using Godot;
namespace BehaviorTree;
[GlobalClass]
public partial class BTSelectNode : BTCompositeNode
{
protected override State Process(double delta)
{
int idx = 0;
for (int i = 0; i < BTChildren.Length; i++)
if (BTChildren[i].CurrentState == State.Running)
{
idx = i;
break;
}
for (int i = idx; i < BTChildren.Length; i++)
{
var node = BTChildren[i];
node.Tick(delta);
if (node.CurrentState != State.Failure) return node.CurrentState;
}
return State.Failure;
}
}
序列节点
using Godot;
namespace BehaviorTree;
[GlobalClass]
public partial class BTSequenceNode : BTCompositeNode
{
protected override State Process(double delta)
{
int idx = 0;
for (int i = 0; i < BTChildren.Length; i++)
if (BTChildren[i].CurrentState == State.Running)
{
idx = i;
break;
}
for (int i = idx; i < BTChildren.Length; i++)
{
var node = BTChildren[i];
node.Tick(delta);
if (node.CurrentState != State.Success) return node.CurrentState;
}
return State.Success;
}
}
装饰节点基类
using Godot;
namespace BehaviorTree;
[GlobalClass]
public abstract partial class BTDecorator : BTNode
{
protected BTNode BTChild { get; private set; }
public override void Init()
{
base.Init();
BTChild = GetChild<BTNode>(0);
}
public override bool Abort(AbortInfo info)
{
if (BTChild.CurrentState == State.Running && BTChild.Abort(info)) return base.Abort(info);
return false;
}
}
条件节点基类
using Godot;
namespace BehaviorTree;
[GlobalClass]
public abstract partial class BTConditionNode : BTNode
{
protected abstract bool Meets();
protected sealed override State Process(double delta) => Meets() ? State.Success : State.Failure;
}
样例
一
一个小怪有如下行为:
- 没有注意到玩家时,在规划好的路线上巡逻
- 注意到玩家后,将持续追逐玩家(注目范围设置为514)
- 跟丢玩家后,将回到路线上巡逻(仇恨范围设置为810)
- 玩家进入攻击范围后,将进行有两秒前摇的攻击(攻击范围设置为114)
它的行为可以描述成下图:

在Godot场景树中可以这样表示(存在用于打log的冗余节点,但大致结构相同):

运行起来看看效果:

二
一个回合制游戏中的小怪,拥有以下行为模式:
- 25%概率对当前生命值最低的友方回复生命值
- 25%概率对全体友方回复生命值
- 25%概率进行普通攻击
- 25%概率蓄力一回合,下一回合进行蓄力攻击;若被打断会眩晕一回合
它的行为可以描述成下图:

这棵树仅在轮到这只怪物行动时执行一次
在Godot场景树中的结构见实现部分第一张图
预览如下:

拓展
构建游戏AI的方法不止行为树一种,此前我们常用的方法是有限状态机,还有HTN、GOAP等方法可以了解与探索,感兴趣的话可以看看 GAMES104讲解游戏AI决策的部分:
- 16.游戏引擎Gameplay玩法系统:基础AI (Part 2) | GAMES104-现代游戏引擎:从入门到实践:有限状态机、行为树
- 17.游戏引擎Gameplay玩法系统:高级AI (Part 1) | GAMES104-现代游戏引擎:从入门到实践:HTN、GOAP、MCTS
参考
概念:https://zhuanlan.zhihu.com/p/463182588
实现:https://github.com/CloverJoyi/KDQS/tree/main/Assets/BehaviorTree